package com.hmdp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.dto.ScrollResult;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.Follow;
import com.hmdp.entity.User;
import com.hmdp.enums.ErrorCodeEnum;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.service.IBlogService;
import com.hmdp.service.IFollowService;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 达人探店服务实现类
 *
 * @author leizi
 */
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    private IUserService userService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private IFollowService followService;

    @Override
    public Result findBlogInfo(Integer blogId) {

        Blog blogInfo = getById(blogId);

        if (ObjectUtils.isEmpty(blogInfo)) {
            return Result.fail(ErrorCodeEnum.BLOG_NOT_FOUND);
        }
        // 补充笔记作者
        Long userId = blogInfo.getUserId();
        User user = userService.getById(userId);
        blogInfo.setName(user.getNickName());
        blogInfo.setIcon(user.getIcon());

        // 查询缓存是否已经点赞(完全依赖缓存存在问题)
        Long currentLoginUserId = UserHolder.getUser().getId();
        Double score = redisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + blogInfo.getId(), String.valueOf(currentLoginUserId));
        if (!ObjectUtils.isEmpty(score)) {
            blogInfo.setIsLike(true);
        }
        return Result.ok(blogInfo);
    }

    @Override
    public Result queryHotBlog(Integer current) {
        // 根据用户查询
        Page<Blog> page = query().orderByDesc("liked").page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        Long currentLoginUserId = UserHolder.getUser().getId();
        // 查询用户
        records.forEach(blog -> {
            Long userId = blog.getUserId();
            User user = userService.getById(userId);
            blog.setName(user.getNickName());
            blog.setIcon(user.getIcon());
            // 查询缓存是否已经点赞(完全依赖缓存存在问题)
            Double score = redisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + blog.getId(), String.valueOf(currentLoginUserId));
            if (!ObjectUtils.isEmpty(score)) {
                blog.setIsLike(true);
            }
        });
        return Result.ok(records);
    }

    /**
     * 完全依赖缓存，点赞状态未入库，可能有缓存丢失风险
     *
     * @param id 笔记id
     */
    @Override
    public void likeBlog(Long id) {
        Long userId = UserHolder.getUser().getId();
        // 获取分数(时间戳)校验是否已经点赞
        Double score = redisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + id, String.valueOf(userId));
        // 已经点赞则取消点赞并数量-1
        if (!ObjectUtils.isEmpty(score)) {
            boolean result = update().setSql("liked = liked - 1").eq("id", id).update();
            if (result) {
                redisTemplate.opsForZSet().remove(RedisConstants.BLOG_LIKED_KEY + id, String.valueOf(userId));
            }
            return;
        }
        // 修改点赞数量
        boolean result = update().setSql("liked = liked + 1").eq("id", id).update();
        if (result) {
            // 未点赞添加缓存，更新笔记点赞，使用ZSet数据类型，值为分数时间戳作为排名
            redisTemplate.opsForZSet().add(RedisConstants.BLOG_LIKED_KEY + id, String.valueOf(userId), System.currentTimeMillis());
        }
    }

    @Override
    public Result queryBlogTop(Integer blogId) {
        Set<String> userIdSet = redisTemplate.opsForZSet().range(RedisConstants.BLOG_LIKED_KEY + blogId, 0, 4);
        if (CollectionUtils.isEmpty(userIdSet)) {
            return Result.ok();
        }
        List<Long> userIdList = userIdSet.stream().map(score -> Long.valueOf(score)).collect(Collectors.toList());
        String idStr = StrUtil.join(",", userIdList);
        List<UserDTO> userTop = userService.query().in("id", userIdList)
                .last("ORDER BY FIELD(id," + idStr + ")")
                .list()
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());

        return Result.ok(userTop);
    }

    @Override
    public Result followUserBlog(Long lastId, Integer offset) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        String key = RedisConstants.FEED_KEY + user.getId();
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, lastId, offset, 2);
        // 3.非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        // 4.解析数据：blogId、minTime（时间戳）、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            // 4.1.获取id
            ids.add(Long.valueOf(tuple.getValue()));
            // 4.2.获取分数(时间戳）
            long time = tuple.getScore().longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }
        os = minTime == lastId ? os : os + offset;
        // 5.根据id查询blog
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

        for (Blog blog : blogs) {
            // 5.1.查询blog有关的用户
            User followUser = userService.getById(blog.getUserId());
            blog.setName(followUser.getNickName());
            blog.setIcon(followUser.getIcon());
            // 5.2.查询blog是否被点赞
            Double score = redisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + blog.getId(), String.valueOf(user.getId()));
            // 已经点赞则取消点赞并数量-1
            if (!ObjectUtils.isEmpty(score)) {
                blog.setIsLike(true);
            } else {
                blog.setIsLike(false);
            }
        }

        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);

        return Result.ok(r);
    }

    /**
     * 采用Feed流 推模式，将博主笔记推送给粉丝用户收件箱
     *
     * @param blog 笔记详情
     * @return
     */
    @Override
    public Result saveBlog(Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店博文
        boolean save = save(blog);
        if (!save) {
            return Result.fail(ErrorCodeEnum.BLOG_SAVE_FAIL);
        }
        // 获取粉丝用户
        List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
        if (!CollectionUtils.isEmpty(follows)) {
            List<Long> userIdList = follows.stream().map(Follow::getUserId).collect(Collectors.toList());
            for (Long userId : userIdList) {
                redisTemplate.opsForZSet().add(RedisConstants.FEED_KEY + userId, String.valueOf(blog.getId()), System.currentTimeMillis());
            }
        }
        return Result.ok(blog.getId());
    }

}
